package cn.dkdoo.utils.templateDefault.mongo;

import cn.dkdoo.utils.config.DkdCheckTraOpenAspect;
import cn.dkdoo.utils.config.exception.DkdTransactionException;
import cn.dkdoo.utils.config.rollbackData.RollbackDataTable;
import cn.dkdoo.utils.config.rollbackData.RollbackDetail;
import cn.dkdoo.utils.config.rollbackData.TraOperationType;
import cn.dkdoo.utils.config.rollbackData.template.RollbackTemplateAbstract;
import cn.dkdoo.utils.config.rollbackData.template.UpdateMulti;
import cn.dkdoo.utils.config.transactionInfo.TransactionInfoAbstract;
import cn.dkdoo.utils.config.transactionalManager.IDoRollback;
import cn.dkdoo.utils.templateCommon.mongo.DkdMongoUtils;
import com.alibaba.fastjson.JSONObject;
import com.mongodb.BasicDBObject;
import com.mongodb.WriteResult;
import com.mongodb.util.JSON;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.*;

import static org.springframework.data.mongodb.core.query.Criteria.where;

/**
 * @author nicky
 * @date 2021/5/14 上午11:12
 */
public class RollbackMongoTemplate extends RollbackTemplateAbstract<MongoTemplate> implements IDoRollback {
    private static final Logger logger = LoggerFactory.getLogger(DkdCheckTraOpenAspect.class);

    public MongoTemplate template;
    public String beanName;

    public RollbackMongoTemplate(MongoTemplate template, String beanName) {
        super(template, beanName);
        this.template = template;
        this.beanName = beanName;
    }

    /**
     * 自定义回滚回调的处理逻辑
     *
     * @param collectionName
     * @param collectionData
     */
    @Override
    public void doRollback(String collectionName, String dataClass, RollbackDataTable collectionData) {
        Map<String, RollbackDetail> tableData = collectionData.getDatas();
        List<Object> addDatas = new LinkedList<>();
        List<String> deleteIds = new LinkedList<>();

        for (Map.Entry<String, RollbackDetail> tableEntry : tableData.entrySet()) {
            String id = "";
            try {
                id = tableEntry.getKey();
                RollbackDetail dataRow = tableEntry.getValue();
                String dataDetail = dataRow.getData() != null ? dataRow.getData().toString() : null;
                int operationType = dataRow.getType();
                if (TraOperationType.ADD.equals(operationType)) {
                    deleteIds.add(id);
                } else if (TraOperationType.DELETE.equals(operationType) || TraOperationType.UPDATE.equals(operationType)) {
                    deleteIds.add(id);
                    if (dataDetail != null) {//upsert 的修改，dataDetail为null
                        Object oldData = null;
                        if (StringUtils.isEmpty(dataClass)) {
                            oldData = parseBasicDBObject(dataDetail);
                        } else {
                            try {
                                oldData = JSONObject.parseObject(dataDetail, Class.forName(dataClass));
                            } catch (Exception ex) {
                                ex.printStackTrace();//手动加入回滚 + 自动回滚 同时存在的时候，会进入这里
                                logger.warn("【mongo回滚】集合：{}, id：{}。尝试二次序列化！", collectionName, id);
                                oldData = parseBasicDBObject(dataDetail);
                            }
                        }
                        addDatas.add(oldData);
                    }
                }
            } catch (Exception ex) {
                // 捕捉异常，尽可能回滚更多的数据
                ex.printStackTrace();
                logger.error("【mongo回滚】集合：{}, id：{}。回滚异常！", collectionName, id);
            }
        }

        if (!CollectionUtils.isEmpty(deleteIds)) {
            this.template.remove(Query.query(where("_id").in(DkdMongoUtils.toObjectIds(deleteIds))), collectionName);
        }

        if (!CollectionUtils.isEmpty(addDatas)) {
//            this.template.insertAll(parse(addDatas), collectionName);
            this.template.insert(addDatas, collectionName);
        }
    }

    public static Object parseBasicDBObject(String dataDetail) {
        BasicDBObject oldData = JSONObject.parseObject(dataDetail, BasicDBObject.class);
        if (oldData.containsField("_id")) {
            oldData.put("_id", new ObjectId(oldData.get("_id").toString()));
        } else if (oldData.containsField("id")) {
            oldData.put("_id", new ObjectId(oldData.get("id").toString()));
            oldData.remove("id");
        }
        //fixme 必须调用原生的parse，否则date等类型会有问题
        return JSON.parse(oldData.toString());
    }

    /**
     * 构造【删除】的回滚
     *
     * @param query
     * @param collectionName
     * @return
     */
    //@DkdCheckTraOpen
    public Map<String, RollbackDetail> removeBuildRol(Query query, String collectionName) {
        if (TransactionInfoAbstract.checkIsClose()) {
            logger.warn("事物已关闭!");
            return null;
        }
        Map<String, RollbackDetail> rollbackDetailMap = new HashMap<>();
        //用DBObject，否则会导致数据类型不一致
        List<BasicDBObject> oldDatas = this.template.find(query, BasicDBObject.class, collectionName);
        addOldDatas(rollbackDetailMap, oldDatas, TraOperationType.DELETE);
        return rollbackDetailMap;
    }

    /**
     * 构造【新增】的回滚
     */
    //@DkdCheckTraOpen
    public Map<String, RollbackDetail> insertBuildRol(Object obj) {
        if (TransactionInfoAbstract.checkIsClose()) {
            logger.warn("事物已关闭!");
            return null;
        }
        return this.insertBuildRol(obj, getFieldOfId(obj));
    }

    private Map<String, RollbackDetail> insertBuildRol(Object obj, Field fieldId) {
        Map<String, RollbackDetail> rollbackDetailMap = new HashMap<>();
        return this.insertBuildRol(obj, fieldId, rollbackDetailMap);
    }

    private Map<String, RollbackDetail> insertBuildRol(Object obj, Field fieldId, Map<String, RollbackDetail> rollbackDetailMap) {
        String idVal = getIdVal(obj, fieldId);
        RollbackDetail rollbackTable = new RollbackDetail(null, TraOperationType.ADD);
        rollbackDetailMap.put(idVal, rollbackTable);
        return rollbackDetailMap;
    }

    //@DkdCheckTraOpen
    public Map<String, RollbackDetail> insertBuildRol(Collection<?> objs) {
        if (TransactionInfoAbstract.checkIsClose()) {
            logger.warn("事物已关闭!");
            return null;
        }
        if (CollectionUtils.isEmpty(objs)) {
            return null;
        }
        Map<String, RollbackDetail> rollbackDetailMap = new HashMap<>();
        Field fieldId = null;
        for (Object obj : objs) {
            if (fieldId == null) {
                fieldId = getFieldOfId(obj);
            }
            this.insertBuildRol(obj, fieldId, rollbackDetailMap);
        }
        return rollbackDetailMap;
    }

    public void insertAllBuildRol(Collection<?> objs) {
        if (CollectionUtils.isEmpty(objs)) {
            return;
        }
        //key-collectionName
        Map<String, List<Object>> objsGroupMap = new HashMap<>();
        for (Object obj : objs) {
            String collectionName = this.template.getCollectionName(obj.getClass());
            List<Object> objGroup = objsGroupMap.get(collectionName);
            if (objGroup == null) {
                objGroup = new ArrayList<>();
                objsGroupMap.put(collectionName, objGroup);
            }
            objGroup.add(obj);
        }

        for (Map.Entry<String, List<Object>> entry : objsGroupMap.entrySet()) {
            Map<String, RollbackDetail> rollbackDetailMap = this.insertBuildRol(entry.getValue());
            super.addRollbackData(entry.getKey(), rollbackDetailMap);
        }
    }

    /**
     * 构造【修改前】的回滚
     *
     * @param query
     * @param collectionName
     * @return
     */
    //@DkdCheckTraOpen
    public Map<String, RollbackDetail> updateBuildRolBef(Query query, String collectionName) {
        if (TransactionInfoAbstract.checkIsClose()) {
            logger.warn("事物已关闭!");
            return null;
        }
        Map<String, RollbackDetail> rollbackDetailMap = new HashMap<>();
        // 只用查询条件+skip+limit即可，include/sort等不需要
        BasicQuery queryAllFields = new BasicQuery(query.getQueryObject()/*, query.getFieldsObject()*/);
        queryAllFields.skip(query.getSkip());
        queryAllFields.limit(query.getLimit());
        List<BasicDBObject> oldDatas = this.template.find(query, BasicDBObject.class, collectionName);
        addOldDatas(rollbackDetailMap, oldDatas, TraOperationType.UPDATE);
        return rollbackDetailMap;
    }

    /**
     * 构造修改后的回滚
     *
     * @param obi
     * @param rollbackDetailMap
     */
    public void updateBuildRolAft(Object obi, Map<String, RollbackDetail> rollbackDetailMap) {
        String idVal = getIdVal(obi);
        updateBuildRolAft(idVal, rollbackDetailMap);
    }

    public void updateBuildRolAft(WriteResult writeResult, Map<String, RollbackDetail> rollbackDetailMap) {
        String idVal = (writeResult != null && writeResult.getUpsertedId() != null) ?
                writeResult.getUpsertedId().toString() : null;
        updateBuildRolAft(idVal, rollbackDetailMap);
    }

    //@DkdCheckTraOpen
    public void updateBuildRolAft(String id, Map<String, RollbackDetail> rollbackDetailMap) {
        if (TransactionInfoAbstract.checkIsClose()) {
            logger.warn("事物已关闭!");
            return;
        }
        if (StringUtils.isEmpty(id) || rollbackDetailMap == null || rollbackDetailMap.containsKey(id)) {
            return;
        }
        // 可能upsert新增了一条数据
        RollbackDetail rollbackTable = new RollbackDetail(null, TraOperationType.DELETE);
        rollbackDetailMap.put(id, rollbackTable);
    }

    private void addOldDatas(Map<String, RollbackDetail> rollbackDetailMap, List<BasicDBObject> oldDatas, TraOperationType type) {
        for (BasicDBObject oldData : oldDatas) {
            if (oldData.containsField("_id")) {
                String id = oldData.get("_id").toString();
                oldData.put("_id", id);//translate Id to Str
                RollbackDetail rollbackTable = new RollbackDetail(oldData.toString(), type);
                rollbackDetailMap.put(id, rollbackTable);
            }
        }
    }

    public String getIdVal(Object obj) {
        return getIdVal(obj, getFieldOfId(obj));
    }

    public String getIdVal(Object obj, Field fieldId) {
        String idVal = null;
        try {
            idVal = String.valueOf(fieldId.get(obj));
        } catch (IllegalAccessException ex) {
            throw new DkdTransactionException("获取id异常");
        }
        return idVal;
    }

    private Field getFieldOfId(Object obj) {
        Field fieldId = null;//todo 缓存Field
        Class classObj = obj.getClass();
        Field[] fields = classObj.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Id id = field.getAnnotation(Id.class);
            if (id != null || field.getName().equals("id")) {
                fieldId = field;
                break;
            }
        }
        return fieldId;
    }


    //todo-nicky 批量修改如何构造
    public void updateBuildRol(String collectionName, UpdateMulti... query) {

    }

    public void addOneRollbackData(Collection<?> oldDatas, Class aClass) {
        if (CollectionUtils.isEmpty(oldDatas)) {
            return;
        }
        String collectionName = this.template.getCollectionName(aClass);
        for (Object oldData : oldDatas) {
            this.addOneRollbackData(oldData, aClass, collectionName);
        }
    }

    protected void addOneRollbackData(Object oldData, Class aClass, String collectionName, boolean saveTra) {
        if (saveTra) {
            this.addOneRollbackData(oldData, aClass, collectionName);
        }
    }

    public void addOneRollbackData(Object oldData, Class aClass, String collectionName) {
        if (oldData == null) {
            return;
        }
        Map<String, RollbackDetail> rollbackDetailMap = new HashMap<>();

        String idVal = getIdVal(oldData);
        //JSON.serialize(oldData) 会报错
        RollbackDetail rollbackTable = new RollbackDetail(JSONObject.toJSONString(oldData), TraOperationType.UPDATE);
        rollbackDetailMap.put(idVal, rollbackTable);

        super.addRollbackData(StringUtils.isEmpty(collectionName) ? this.template.getCollectionName(aClass) : collectionName, aClass.getName(), rollbackDetailMap);
    }

    public void addOneRollbackData(Object oldData, Class aClass) {
        this.addOneRollbackData(oldData, aClass, null);
    }

}
